/*
 * angular-image-lazyload - v0.0.1 - 2015-10-14
 * https://github.com/VanMess/angular-image-lazyload
 * Copyright (c) 2014 Van (http://vanmess.github.io/)
 */
!(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['angular'], factory);
    } else {
        // Global Variables
        factory(window.angular);
    }
})(function (ng) {
    'use strict';

    var DONT_ENUM = "propertyIsEnumerable,isPrototypeOf,hasOwnProperty,toLocaleString,toString,valueOf,constructor".split(","),
        hasOwn = ({}).hasOwnProperty;
    for (var i in {
        toString: 1
    }) {
        DONT_ENUM = false;
    }
    Object.keys = Object.keys || (function (obj) { //ecma262v5 15.2.3.14
        return function (obj) {
            var result = [],
                key;
            for (key in obj) {
                if (hasOwn.call(obj, key)) {
                    result.push(key);
                }
            }
            if (DONT_ENUM && obj) {
                for (var i = 0; i < DONT_ENUM.length; i++) {
                    key = DONT_ENUM[i++];
                    if (hasOwn.call(obj, key)) {
                        result.push(key);
                    }
                }
            }
            return result;
        };
    })();

    if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
            if (typeof this !== "function") {
                // closest thing possible to the ECMAScript 5 internal IsCallable function
                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
            }
            var aArgs = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP = function () { },
                fBound = function () {
                    return fToBind.apply(this instanceof fNOP && oThis ? this : oThis,
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        };
    }

    // Create all modules and define dependencies to make sure they exist
    // and are loaded in the correct order to satisfy dependency injection
    // before all nested files are concatenated by Gulp

    angular.module('vgSrc', []);

    /*
     *  The provider of this module, just for setting default config.
     */
    var

        //  default setting
        defaults = {
            debug: false,


            // element would applied this class when it is loading.
            loadingCls: '',

            // element would applied this class when it is load success.
            loadedCls: '',

            //  place image content when image resource fail to load.
            error: '',

            // element would applied this class when it faild to load.
            errorCls: '',

            //  place image content when element's "src" attribute is empty(null,undefine).
            empty: '',

            // element would applied this class when src attribute is empty(null,undefine).
            emptyCls: '',

            // event occurs when it start to load something.
            'onBegin': ng.noop,

            // event occurs when it fail load something.
            'onError': ng.noop,

            // event occurs when it finish to load something.
            'onLoad': ng.noop
        },

        provider = function () {
            var moduleConfig = ng.copy(defaults),
                result = {
                    $set: _setConfig,
                    $get: function () {
                        return moduleConfig;
                    }
                };

            return result;

            function _setConfig(cfg) {

                if (!ng.isDefined(cfg)) return;

                ng.forEach(Object.keys(moduleConfig), function (key) {
                    if (cfg.hasOwnProperty(key) && key !== 'debug') {
                        moduleConfig[key] = cfg[key] + '';
                    }
                });

                // init debug model
                if (cfg.hasOwnProperty('debug')) {
                    moduleConfig.debug = !!cfg.debug;
                }

                // init events
                var events = ['onBegin', 'onError', 'onLoad'],
                    tmp;
                ng.forEach(events, function (e) {
                    tmp = ng.isFunction(cfg[e]) ? cfg[e] : ng.noop;
                    moduleConfig[e] = (function (_old) {
                        return function ($scope, $e) {
                            _old($e);
                        };
                    })(tmp);
                });
            }
        };

    angular.module('vgSrc').provider('vgSrcConfig', [provider]);

    /*
     *  An angular directive for image loading status present.
     */
    var directive = function ($parse, defaults) {
        var status = ['loading', 'empty', 'error'],
            statusCls = ['loadingCls', 'emptyCls', 'errorCls', 'loadedCls'],
            events = ['onBegin', 'onError', 'onLoad'],
            // angular's directive define object.
            defineObj = {
                priority: 99,
                restrict: 'A',
                name: 'vgSrc',
                compile: function (element, attrs) {
                    var attrName = attrs.$normalize(defineObj.name),
                        srcParser = $parse(attrs[attrName]);

                    return function _link($scope, element, attrs) {
                        var opt = ng.copy(defaults),
                            $log = opt.debug ? console.log.bind(console) : ng.noop;
                        // parse everything in status lists.
                        ng.forEach(status, function (att) {
                            if (ng.isString(attrs[att])) {
                                // parse element's setting attribute use ng's '$parse'
                                // so that users can define the configuration by ng's 'expression'.
                                opt[att] = $parse(attrs[att])($scope);
                            }
                        });

                        // simply copy everything in statusCls lists.
                        ng.forEach(statusCls, function (att) {
                            if (ng.isString(attrs[att])) {
                                opt[att] = attrs[att];
                            }
                        });

                        // parse event handlers
                        // so that we can occu
                        ng.forEach(events, function (att) {
                            if (ng.isString(attrs[att])) {
                                opt[att] = $parse(attrs[att]);
                            }
                        });

                        // watching vgSrc attribute
                        // so that we could dynamicly fresh element's image when each time user change the value
                        $scope.$watch(function () {
                            return srcParser($scope);
                        }, function _bindImg(newVal, oldVal) {
                            var $e = {
                                src: newVal
                            };
                            if (ng.isString(newVal) && newVal.length > 0) {
                                if (!opt['errorCls']) {
                                    opt['errorCls'] = "ps_b_loading";
                                }
                                if (!opt['loadingCls']) {
                                    opt['loadingCls'] = "ps_b_loading";
                                }
                                if (!opt['emptyCls']) {
                                    opt['emptyCls'] = "ps_b_loading";
                                }

                                attrs.$set('src', opt.loading);
                                _refreshCls(opt['loadingCls']);
                                opt['onBegin'].call($scope, $scope, $e);
                                $log('start loading resource:' + $e.src);

                                _lazyLoad(newVal, function () {
                                    attrs.$set('src', newVal);
                                    _refreshCls(opt['loadedCls']);
                                    opt['onLoad'].call($scope, $scope, $e);
                                    $log('success load resource:' + $e.src);
                                }, function () {
                                    attrs.$set('src', opt.error);
                                    _refreshCls(opt['errorCls']);
                                    opt['onError'].call($scope, $scope, $e);
                                    $log('failure load resource:' + $e.src);
                                })
                            } else {
                                attrs.$set('src', opt.empty);
                                _refreshCls(opt['emptyCls']);
                                opt['onError'].call($scope, $scope, $e);
                                $log('current img is empty');
                            }
                        });

                        // clear element's status class
                        // and add the new class
                        function _refreshCls(cls) {
                            ng.forEach(statusCls, function (cls) {
                                element.removeClass(opt[cls]);
                            });
                            element.addClass(cls);
                        }
                    };

                }
            };

        return defineObj;
    };

    angular.module('vgSrc').directive('vgSrc', ['$parse', 'vgSrcConfig', directive]);

    /*
     *  load function to excute a shadow load
     */
    function _lazyLoad(src, loadCallback, errorCallback) {
        var $imgDom = ng.element(new Image());
        loadCallback = ng.isFunction(loadCallback) ? loadCallback : ng.noop;
        errorCallback = ng.isFunction(errorCallback) ? errorCallback : ng.noop;
        $imgDom.bind('error', errorCallback.bind(this)).bind('load', loadCallback.bind(this)).attr('src', src);
    }

});
